Вложенная производительность Linq - PullRequest
0 голосов
/ 01 ноября 2010

У нас есть 2 таблицы (tblSerials и tblRequests). Вопрос в том, как заставить этот код выполняться быстрее? Как вы можете видеть, «из запроса в ctx.tblRequests» части очень похожи. Любое другое лучшее решение?

var list = from s in ctx.tblSerials
        orderby s.CreateDate descending
        select new
        {
         SerialGUID = s.SerialGUID,
         CreateDate = s.CreateDate,
         NumberOfAllRequests = 
            (from request in ctx.tblRequests
              where request.SerialGUID.ToLower().Equals(s.SerialGUID.ToLower())
              select request.RequestGUID).Count(),
         NumberOfAllRequestsDistinctByMachine = 
            (from request in ctx.tblRequests
              where request.SerialGUID.ToLower().Equals(s.SerialGUID.ToLower())
              select request.MachineCode).Distinct().Count(),                    
        };

Ответы [ 6 ]

2 голосов
/ 06 ноября 2010

Если ваша цель - производительность, как выглядит план запроса, сгенерированный SQL Server?Пятый шаг из этой статьи предоставляет некоторую помощь в том, как найти план запроса.Возможно, что после того, как вы примете некоторые изменения в других ответах, замедление будет не из-за L2Sql, а из-за неправильной структуры базы данных.

Например, индексирован ли SerialGuid?Если в таблице достаточно записей, получение запроса для поиска по индексу может обеспечить ускорение на порядок.

2 голосов
/ 06 ноября 2010

Мой опыт показывает, что Linq to SQL всегда означает значительное снижение производительности, особенно со многими запросами, поэтому рассмотрите вариант использования вместо этого хранимых процедур на сервере базы данных и просто перенесите результат в новый List с ToList () на(возможно, в зависимости от того, какие методы вы используете) Результат запроса.

2 голосов
/ 01 ноября 2010

Если вы не хотите видеть результаты с 0 счетчиками в tblRequests, вы можете использовать этот запрос:

tblSerials
    .Join(
        tblRequests,
        x => x.SerialGUID.ToLower(),
        x => x.SerialGUID.ToLower(),
        (o,i) => new { Serial = o, Request = i }
    ).GroupBy(x => x.Serial)
    .Select(x => new {
        SerialGUID = x.Key.SerialGUID,
        CreateDate = x.Key.CreateDate,
        NumberOfAllRequests = x.Select(y => y.Request.RequestGUID).Count(),
        NumberOfAllRequestsDistinctByMachine = x.Select(y => y.Request.MachineCode).Distinct().Count()
    }).OrderByDescending(x => x.CreateDate);

Если вы хотите получить эти результаты, вы можете легко вставить одну из таблиц в память и использовать многострочную лямбда-функцию:

tblSerials
    .ToList().Select(x => {
    var requests = tblRequests.Where(y => y.SerialGUID.ToLower().Equals(x.SerialGUID.ToLower()));
    return  new {
                SerialGUID = x.SerialGUID,
                CreateDate = x.CreateDate,
                NumberOfAllRequests = requests.Count(),
                NumberOfAllRequestsDistinctByMachine = requests.Select(y => y.MachineCode).Distinct().Count()
            };
    }).OrderByDescending(x => x.CreateDate);

Или, если tblSerials слишком велик, вы можете использовать внешнее соединение:

(from s in tblSerials
from r in tblRequests
    .Where(x => x.SerialGUID.ToLower().Equals(s.SerialGUID.ToLower()))
    .DefaultIfEmpty()
select new { Serial = s, Request = r })
.GroupBy(x => x.Serial)
    .Select(x => new {
        SerialGUID = x.Key.SerialGUID,
        CreateDate = x.Key.CreateDate,
        NumberOfAllRequests = x.Any(y => y.Request != null) ? x.Select(y => y.Request.RequestGUID).Count() : 0,
        NumberOfAllRequestsDistinctByMachine = x.Any(y => y.Request != null) ? x.Select(y => y.Request.MachineCode).Distinct().Count() : 0
    }).OrderByDescending(x => x.CreateDate);

ПРИМЕЧАНИЕ. На самом деле у меня нет никаких сравнений производительности, и я не могу гарантировать, что что-то из этого будет быстрее, чем у вас, это всего лишь предложения.

(если у вас установлен linqpad , вы можете сравнить результаты всех этих и ваших собственных запросов с некоторыми фиктивными данными в следующем скрипте:

var tblSerials = new [] {
new { CreateDate = DateTime.Today.AddDays(-2), SerialGUID = "foo" },
new { CreateDate = DateTime.Today.AddDays(-3), SerialGUID = "bar" },
new { CreateDate = DateTime.Today.AddDays(-2), SerialGUID = "foobar" },
new { CreateDate = DateTime.Today.AddDays(-1), SerialGUID = "foo" }
};

var tblRequests = new [] {
new { SerialGUID = "foo", RequestGUID = "hi", MachineCode = "1" },
new { SerialGUID = "bar", RequestGUID = "yo", MachineCode = "2" },
new { SerialGUID = "foo", RequestGUID = "hello", MachineCode = "1" },
new { SerialGUID = "baz", RequestGUID = "yeah", MachineCode = "3" }
};

tblSerials
    .Join(
        tblRequests,
        x => x.SerialGUID.ToLower(),
        x => x.SerialGUID.ToLower(),
        (o,i) => new { Serial = o, Request = i }
    ).GroupBy(x => x.Serial)
    .Select(x => new {
        SerialGUID = x.Key.SerialGUID,
        CreateDate = x.Key.CreateDate,
        NumberOfAllRequests = x.Select(y => y.Request.RequestGUID).Count(),
        NumberOfAllRequestsDistinctByMachine = x.Select(y => y.Request.MachineCode).Distinct().Count()
    }).OrderByDescending(x => x.CreateDate).Dump();

(from s in tblSerials
from r in tblRequests
    .Where(x => x.SerialGUID.ToLower().Equals(s.SerialGUID.ToLower()))
    .DefaultIfEmpty()
select new { Serial = s, Request = r })
.GroupBy(x => x.Serial)
    .Select(x => new {
        SerialGUID = x.Key.SerialGUID,
        CreateDate = x.Key.CreateDate,
        NumberOfAllRequests = x.Any(y => y.Request != null) ? x.Select(y => y.Request.RequestGUID).Count() : 0,
        NumberOfAllRequestsDistinctByMachine = x.Any(y => y.Request != null) ? x.Select(y => y.Request.MachineCode).Distinct().Count() : 0
    }).OrderByDescending(x => x.CreateDate).Dump();

tblSerials
    .ToList().Select(x => {
    var requests = tblRequests.Where(y => y.SerialGUID.ToLower().Equals(x.SerialGUID.ToLower()));
    return  new {
                SerialGUID = x.SerialGUID,
                CreateDate = x.CreateDate,
                NumberOfAllRequests = requests.Count(),
                NumberOfAllRequestsDistinctByMachine = requests.Select(y => y.MachineCode).Distinct().Count()
            };
    }).OrderByDescending(x => x.CreateDate).Dump();

var list = from s in tblSerials
        orderby s.CreateDate descending
        select new
        {
         SerialGUID = s.SerialGUID,
         CreateDate = s.CreateDate,
         NumberOfAllRequests = 
            (from request in tblRequests
              where request.SerialGUID.ToLower().Equals(s.SerialGUID.ToLower())
              select request.RequestGUID).Count(),
         NumberOfAllRequestsDistinctByMachine = 
            (from request in tblRequests
              where request.SerialGUID.ToLower().Equals(s.SerialGUID.ToLower())
              select request.MachineCode).Distinct().Count(),                    
        };

list.Dump();
1 голос
/ 01 ноября 2010

Самый простой способ сделать это быстро - перенести данные в память. Тем не менее, несмотря на то, что результаты запроса вполне вероятно попадут в память, исходные данные могут не совпадать. Таким образом, хитрость заключается в том, чтобы ограничить данные, загружаемые в память, но обрабатывать предложения where в базе данных, но выполнять ваши предложения join в памяти.

В этом случае, однако, нет предложений where для отправки в базу данных, поэтому я предполагаю, что все tblSerials & tblRequests поместится в памяти.

Вот что нужно сделать:

var serials =
    ctx.tblSerials.ToArray();

var requests =
    ctx.tblRequests.ToArray();

var list =
    from s in serials 
    orderby s.CreateDate descending
    let rs =
        (from request in requests
         where request.SerialGUID.ToLower().Equals(s.SerialGUID.ToLower())
         select request).ToArray()
    let NumberOfAllRequests =
        rs.Count()
    let NumberOfAllRequestsDistinctByMachine =
        rs.Select(r => r.MachineCode).Distinct().Count()
    select new
    {
        s.SerialGUID,
        s.CreateDate,
        NumberOfAllRequests,
        NumberOfAllRequestsDistinctByMachine,
    };

Это должен быть самый быстрый способ выполнить ваш запрос - при условии, что он может загружаться в память.

1 голос
/ 01 ноября 2010

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

вроде так:

    var requestsByMachine = (from request in ctx.tblRequests
                            group request by request.MachineCode into g
                            select new { serial = request.SerialGuid, amount = g.Distinct().Count() }).ToArray();

    var requestsByGuid = (from request in ctx.tblRequests
                         group request by request.requestGUID into g
                         select new { serial = request.SerialGuid, amount = g.Count()}).ToArray();

    var list = from s in ctx.tblSerials.ToArray()
               join r1 in requestsByGuid on s.SerialGUID equals r1.serial
               join r2 in requestsByMachine on s.SerialGUID equals r2.serial
               orderby s.CreateDate descending
               select new
               {
                   SerialGUID = s.SerialGUID,
                   CreateDate = s.CreateDate,
                   NumberOfAllRequests = r1.amount,
                   NumberOfAllRequestsDistinctByMachine = r2.amount
               };

Не могу попробовать сам сейчас, но вы можете проверить в sql profiler, чтобы увидеть, создает ли один запрос.

Редактировать: изменено для запроса в памяти

1 голос
/ 01 ноября 2010

Если вы присоединитесь к tblRequests, вы можете изменить свой выбор на:

select new
{
    SerialGUID = s.SerialGUID,
    CreateDate = s.CreateDate,
    RequestGUID = request.RequestGUID,
    MachineCode = request.MachineCode
};

Тогда у вас есть один запрос.После получения этих результатов вы можете затем «вручную» агрегировать их по набору результатов, и вы сами Count() и Distinct().Count().

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...