Выбор агрегированной (сгруппированной) статистики с записями в запросе LINQ-to-Entities - PullRequest
0 голосов
/ 04 ноября 2010

У меня есть запрос, который я пытаюсь перенести с SQL (T-SQL) на LINQ-to-Entities 4.0 (C #). Результирующий набор содержит комбинацию стандартных «строк сведений», а также совокупную «статистическую» информацию.

В исходном SQL использовался стандартный выбор, соединенный слева с информацией о совокупности, подобный этому:

SELECT 
    UserId, 
    Name, 
    Email, 
    ISNULL(Stats.TotalPosts, 0) as TotalPosts,
    Stats.LastPost
FROM Users
LEFT OUTER JOIN
(
    SELECT UserId, COUNT(*) as TotalPosts, MAX(DatePosted) as LastPost
    FROM Articles
    GROUP BY UserId
) as Stats ON Stats.UserId = Users.UserID

По соображениям производительности вместо подзапросов в операторе SELECT используется левое объединение - возвращается более одной агрегированной статистики (общее количество сообщений и дата последнего сообщения)

У меня был некоторый частичный успех при преобразовании его в запрос LINQ-to-Entities в C # 4.0, но я не совсем уверен, как соединение должно связываться с оператором группы. Я думаю, что думаю об этом с точки зрения SQL и неправильно использую LINQ.

Мне удалось разбить статистику на отдельный запрос:

var stats =
(
    from a in entities.Articles
    group a by a.UserId into g
    select new
    {
        UserId = g.Key,
        TotalPosts = g.Count(),
        LastUpdated = g.Max(i => i.DatePosted)
    }
);

var query =
(
    from u in entities.Users
    join s in stats on u.UserId equals s.UserId
    orderby u.Name 
    select new UserListing()
    {
        UserId = u.UserId,
        Name = u.Name,
        Email = u.Email,
        TotalPosts = s.TotalPosts,
        LastUpdated = s.LastUpdated
    }
);

К сожалению, объединение, используемое в запросе LINQ, отфильтровывает всех пользователей, которые не отправили ни одной статьи.

Переключение на эквивалент внешнего соединения путем включения DefaultIfEmpty вызывает другие проблемы - я могу только вернуть «null» для TotalPosts вместо 0. Даже с «TotalPosts = (s.TotalPosts == null)? msgstr "в выборе выбрасывается исключение, если свойство TotalPosts не может иметь значение null.

Каковы наилучшие практики для объединения строк сведений и агрегирования информации таким образом?

Спасибо!

Ответы [ 3 ]

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

Один из возможных вариантов - убедиться, что соответствующие свойства в запросе stats обнуляются.LINQ-to-entity внесет необходимые коррективы, чтобы сделать эту работу, если это вообще возможно.Затем выполните левое внешнее соединение как обычно.

var stats =
(
    from a in entities.Articles
    group a by a.UserId into g
    select new
    {
        UserId = g.Key,
        TotalPosts = (int?)g.Count(),
        LastUpdated = g.Max(i => i.DatePosted)
    }
);

var query =
(
    from u in entities.Users
    join s in stats on u.UserId equals s.UserId into joinedStats
    from s in joinedStats.DefaultIfEmpty() // do left outer join
    orderby u.Name 
    select new UserListing()
    {
        UserId = u.UserId,
        Name = u.Name,
        Email = u.Email,
        TotalPosts = s.TotalPosts,  // null if doesn't contain stats
        LastUpdated = s.LastUpdated // default DateTime if doesn't contain stats
    }
);
1 голос
/ 05 ноября 2010

Попробуйте это:

var query =
(
    from u in entities.Users
    join s in stats on u.UserId equals s.UserId into g
    from a in g.DefaultIfEmpty()
    orderby u.Name 
    select new UserListing()
    {
        UserId = u.UserId,
        Name = u.Name,
        Email = u.Email,
        TotalPosts = a.TotalPosts,
        LastUpdated = a.LastUpdated
    }
);
0 голосов
/ 05 ноября 2010

Чтобы получить внешнее соединение, вам нужно использовать DefaultIfEmpty.Чтобы решить нулевую проблему, вы можете попробовать

TotalPosts = s.TotalPosts.GetValueOrDefault(),

или, если s.TotalPosts как-то не появляется как int?, вы можете попробовать взломать как

TotalPosts = ((int?)s.TotalPosts).GetValueOrDefault(0),
...