Какая модель быстрее всего использует linq, отношения с внешним ключом или локальные списки? - PullRequest
4 голосов
/ 25 сентября 2011

Некоторые основы

У меня есть две таблицы, одна из которых содержит пользователей, а другая - журнал с логинами.Таблица пользователей содержит примерно 15 000+ пользователей, таблица входа в систему растет и достигает 150000+ сообщений.База данных построена на SQL Server (не экспресс).

Для администрирования пользователей я получил сетку (ASPxGridView от Devexpress), которую я заполняю из ObjectDatasource.

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

Все становится странно медленным.

Вот изображение, показывающее соответствующие таблицы.enter image description here

Я пробовал несколько вещей.

DbDataContext db = new DbDataContext();

// Using foregin key relationship
foreach (var proUser in db.tblPROUsers)
{
    var count = proUser.tblPROUserLogins.Count;
    //...
}

Время выполнения: 01: 29.316 (1 минута и 29 секунд)

// By storing a list in a local variable (I removed the FK relation)
var userLogins = db.tblPROUserLogins.ToList();
foreach (var proUser in db.tblPROUsers)
{
    var count = userLogins.Where(x => x.UserId.Equals(proUser.UserId)).Count();
    //...
}

Время выполнения: 01: 18,410 (1 минута и 18 секунд)

// By storing a dictionary in a local variable (I removed the FK relation)
var userLogins = db.tblPROUserLogins.ToDictionary(x => x.UserLoginId, x => x.UserId);
foreach (var proUser in db.tblPROUsers)
{
    var count = userLogins.Where(x => x.Value.Equals(proUser.UserId)).Count();
    //...
}

Время выполнения: 01: 15,821 (1 минута и 15 секунд)

Модель, дающая наилучшую производительность, фактически является словарем.Тем не менее, я знаю о любых вариантах, о которых мне хотелось бы услышать, даже если есть что-то «плохое» с этим типом кодирования при обработке таких больших объемов данных.

Спасибо

========================================================

ОБНОВЛЕНО С моделью в соответствии с примером BrokenGlass

// By storing a dictionary in a local variable (I removed the FK relation)
foreach (var proUser in db.tblPROUsers)
{
    var userId = proUser.UserId;
    var count = db.tblPROUserLogins.Count(x => x.UserId.Equals(userId));
    //...
}

Время выполнения: 02: 01.135 (2 минуты и 1 секунда)

В дополнение к этому я создал список, хранящий простой класс

public class LoginCount
{
    public int UserId { get; set; }
    public int Count { get; set; }
}

И в методе суммирования

var loginCount = new List<LoginCount>();

// This foreach loop takes approx 30 secs
foreach (var login in db.tblPROUserLogins)
{
    var userId = login.UserId;

    // Check if available
    var existing = loginCount.Where(x => x.UserId.Equals(userId)).FirstOrDefault();
    if (existing != null)
        existing.Count++;
    else
        loginCount.Add(new LoginCount{UserId = userId, Count = 1});
}

// Calling it
foreach (var proUser in tblProUser)
{
    var user = proUser;
    var userId = user.UserId;

    // Count logins
    var count = 0;
    var loginCounter = loginCount.Where(x => x.UserId.Equals(userId)).FirstOrDefault();
    if(loginCounter != null)
        count = loginCounter.Count;
    //...
}

Время выполнения: 00: 36.841 (36 секунд)

Заключение До сих пор подведение итогов с помощью linq идет медленно, но я добираюсь до цели!

Ответы [ 3 ]

3 голосов
/ 25 сентября 2011

Возможно, было бы полезно, если бы вы попытались создать SQL-запрос, который делает то же самое и выполняет его независимо от вашего приложения (в SQL Server Management Studio).Что-то вроде:

SELECT UserId, COUNT(UserLoginId)
FROM tblPROUserLogin
GROUP BY UserId

(ПРИМЕЧАНИЕ. При этом просто выбирается UserId. Если вы хотите, чтобы другие поля были из tblPROUser, вам понадобится простой JOIN "поверх" этого базового запроса..)

Убедитесь, что в {UserId, UserLoginId} есть составной индекс , и он используется планом запроса.Наличие обоих полей в индексе и в таком порядке гарантирует, что ваш запрос может выполняться без прикосновения к таблице tblPROUserLogin:

enter image description here

Затем выполните тест и посмотрите, сможете ли вы получить значительно лучшее времячем ваш код LINQ:

  • Если да, то вам нужно найти способ «уговорить» LINQ для генерации аналогичного запроса.
  • Если нет, то выуже так быстро, как вы когда-либо будете.

--- EDIT ---

Следующий фрагмент LINQ эквивалентен приведенному выше запросу:

var db = new UserLoginDataContext();

db.Log = Console.Out;

var result =
    from user_login in db.tblPROUserLogins
    group user_login by user_login.UserId into g
    select new { UserId = g.Key, Count = g.Count() };

foreach (var row in result) {
    int user_id = row.UserId;
    int count = row.Count;
    // ...
}

, который печатает следующий текст в консоли:

SELECT COUNT(*) AS [Count], [t0].[UserId]
FROM [dbo].[tblPROUserLogin] AS [t0]
GROUP BY [t0].[UserId]
-- Context: SqlProvider(Sql2008) Model: AttributedMetaModel Build: 4.0.30319.1

--- EDIT 2 ---

Чтобы иметь "целый "пользователь, а не только UserId, вы можете сделать это:

var db = new UserLoginDataContext();

db.Log = Console.Out;

var login_counts =
    from user_login in db.tblPROUserLogins
    group user_login by user_login.UserId into g
    select new { UserId = g.Key, Count = g.Count() };

var result =
    from user in db.tblPROUsers
    join login_count in login_counts on user.UserId equals login_count.UserId
    select new { User = user, Count = login_count.Count };

foreach (var row in result) {
    tblPROUser user = row.User;
    int count = row.Count;
    // ...
}

И вывод консоли показывает следующий запрос ...

SELECT [t0].[UserId], [t0].[UserGuid], [t0].[CompanyId], [t0].[UserName], [t0].[UserPassword], [t2].[value] AS [Count]
FROM [dbo].[tblPROUser] AS [t0]
INNER JOIN (
    SELECT COUNT(*) AS [value], [t1].[UserId]
    FROM [dbo].[tblPROUserLogin] AS [t1]
    GROUP BY [t1].[UserId]
    ) AS [t2] ON [t0].[UserId] = [t2].[UserId]
-- Context: SqlProvider(Sql2008) Model: AttributedMetaModel Build: 4.0.30319.1

... который должен бытьочень эффективно, если ваши индексы верны:

enter image description here

1 голос
/ 25 сентября 2011

Извините, делаю это вслепую, так как я не на своем обычном компьютере.Всего пара вопросов

  • у вас есть индекс по идентификатору пользователя в таблице логинов
  • вы пробовали просмотр, специально созданный для этой страницы?Вы используете пейджинг, чтобы получить пользователей, или пытаетесь получить все подсчеты сразу?
  • Вы запустили SQL Server Profiler и смотрели фактический отправляемый SQL?работа для тебя?
    var allOfIt = from c in db.tblProUsers 
            select new {
                 User  = c, 
                 Count = db.tblProUserLogins.Count(l => l.UserId == c.UserId)
            }
            .Skip(pageSize * pageNumber)
            .Take(pageSize) // page size
    
1 голос
/ 25 сентября 2011

Второй регистр должен всегда быть самым быстрым, если вы отбросите ToList(), так что подсчет может быть выполнен на стороне базы данных, а не в памяти:

var userId = proUser.UserId;
var count = db.tblPROUserLogins.Count(x => x.UserId == userId);

Также вы должны сначала поместить идентификатор пользователя в «простую» примитивную переменную, так как EF не может иметь дело со свойствами отображения объекта.

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